﻿package 
{
	import flash.display.*;
	import flash.events.*;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	
	/** Tool is used to snap segments into parallel lines, if within margin of error */
	public class ParallelTool extends CursorTool
	{
		// constants
		public const MAGNET_WIDTH:int = 10;
		public const MAGNET_HEIGHT:int = 40;
		public const ARM_WIDTH:int = 6;
		public const MAX_WIGGLE_PROP:Number = .05; // the percent of total wiggle that may be conducted in a single frame
		// public vars
		public var marginOfError_snap:Number, marginOfError_parallel:Number; // Number of pixels to snap to segment, Number of degrees in which parallel lines will snap
		// private objects
		private var drawSegments:Array, oobshapes:Array;
		private var overSegment:DrawSegment, selectedSegment:DrawSegment;
		private var magnet1:Shape, magnet2:Shape, arm:Shape;
		private var _segment1:DrawSegment = null;
		private var _segment2:DrawSegment = null;
		private var point2:Point; //point2
		// private vars
		private var _state:String;
		private var _outOfBounds:Boolean = false;
		private var magnet1good:Boolean = false; // flag for drawing a (blue?) outline around an unset magnet2 indicating that it is ready to be set
		private var magnet2good:Boolean = false; // flag for drawing a (blue?) outline around an unset magnet2 indicating that it is ready to be set
		private var magnet2bad:Boolean = false; // flag for drawing a (red?) outline around an unset magnet2 indicating that it cannot match the given segment
		private var distI:Number; // the closest distance between the mouse point and this tool along a perpendicular line from chosen segment1
		private var distFixed:Number; // same as distI, but once second segment has been chosen this distance does not change;
		private var marginOfErrorMessageGiven:Boolean=false;
		private var endpointsPinnedMessageGiven:Boolean=false;
		// for the "wiggle"
		private var initialError:Number; // the error, in degrees, between two "parallel" sides, before wiggle
		private var targetWiggle:Number; // how far the user needs to wiggle, by default min of two segments
		private var currentWiggle:Number; // how far the user has currently wiggled, in eighther direction
		private var previousWigglePoint:Point; // the previous point of this tool so we know how far we travelled in current wiggle
		
		override public function get outOfBounds():Boolean{return _outOfBounds;}
		public function get component1():DrawSegment{return _segment1;}
		public function set component1(s:DrawSegment):void{this._segment1 = s;}
		public function get component2():DrawSegment{return _segment2;}
		public function set component2(s:DrawSegment):void{this._segment2 = s;}
		override public function get components():Array
		{
			var arr:Array = new Array();
			if (_segment1 != null) arr.push(_segment1);
			if (_segment2 != null) arr.push(_segment2);
			return arr;
		}
		/** Constructor with margin of error variables */
		public function ParallelTool (drawSegments:Array, oobshapes:Array, marginOfError_parallel:Number, marginOfError_snap:Number=10)
		{
			super(this);
			this.drawSegments = drawSegments;
			this.oobshapes = oobshapes;
			this.mouseEnabled = false;
			this.mouseChildren = false;
			this.marginOfError_snap = marginOfError_snap;
			this.marginOfError_parallel = marginOfError_parallel;
			this.addEventListener(Event.ADDED, handleAdded);
		}
		private function handleAdded(event:Event):void
		{
			this.removeEventListener(Event.ADDED, handleAdded);
			_state = "zero";
			drawCursor();
			// go through each point and add event listener
			for each (var s:DrawSegment in drawSegments)
			{				
				s.mouseEnabled = true;
				if(!s.hasEventListener(MouseEvent.MOUSE_OVER)) s.addEventListener(MouseEvent.MOUSE_OVER, handleMouseOverSegment);				
			}
			for each (var oobshape:OobShape in oobshapes)
			{
				oobshape.addEventListener(MouseEvent.MOUSE_OVER, handleMouseOverOobShape);				
			}
			this.addEventListener(Event.REMOVED, handleRemoved);
		}
		
		//////////////////////// INTERACTIVITY ////////////
		/** Mouse over should work for all segments to highlight except currently selected, unless allow disconnected */
		private function handleMouseOverSegment(event:MouseEvent):void
		{
			overSegment = DrawSegment(event.currentTarget);
			overSegment.removeEventListener(MouseEvent.MOUSE_OVER, handleMouseOverSegment);
			overSegment.addEventListener(MouseEvent.MOUSE_OUT, handleMouseOutSegment);
			overSegment.addEventListener(MouseEvent.CLICK, handleClickSegment);
		}
		private function handleMouseOutSegment(event:MouseEvent):void
		{
			overSegment.removeEventListener(MouseEvent.MOUSE_OUT, handleMouseOutSegment);
			overSegment.removeEventListener(MouseEvent.CLICK, handleClickSegment);
			overSegment.addEventListener(MouseEvent.MOUSE_OVER, handleMouseOverSegment);
			overSegment = null;
		}
		private function handleClickSegment (event:MouseEvent):void
		{
			if (selectedSegment != overSegment)
			{
				selectedSegment = overSegment;
				selectComponent(selectedSegment);
			} else
			{
				selectedSegment = null;
				clearComponent(selectedSegment);
			}			
		}
		
		// out of bounds objects
		private function handleMouseOverOobShape(event:MouseEvent):void
		{
			event.currentTarget.removeEventListener(MouseEvent.MOUSE_OVER, handleMouseOverOobShape);
			_outOfBounds = true;
			event.currentTarget.addEventListener(MouseEvent.MOUSE_OUT, handleMouseOutOobShape);
		}
		private function handleMouseOutOobShape(event:MouseEvent):void
		{
			event.currentTarget.removeEventListener(MouseEvent.MOUSE_OUT, handleMouseOutOobShape);
			_outOfBounds = false;
			event.currentTarget.addEventListener(MouseEvent.MOUSE_OVER, handleMouseOverOobShape);
		}
				
		/** Assigns segment1 then segment2 */
		override public function selectComponent (c:MovieClip):void
		{
			var s:DrawSegment = DrawSegment(c);
			if (_segment1 == null)
			{
				_segment1 = s;
				_state = "one";
				drawCursor();
			} else if (_segment2 == null)
			{
				//within margin of error?
				if (magnet2good)
				{
					_segment2 = s;
					distFixed = distI;
					_state = "two";
					// setup wiggle
					initialError = Math.abs((_segment2.rotation+180)%180 - (rotation+180)%180);
					targetWiggle = 4*Math.min(_segment1.length,_segment2.length);
					currentWiggle = 0;
					previousWigglePoint = new Point(this.x, this.y);
				}
			} else
			{
				trace("only two may be selected");
			}
		}
		/** Removes selected segment */
		override public function clearComponent (component:MovieClip):void
		{
			var s:DrawSegment = DrawSegment(component);
			if (_segment2 != null && _segment2 == s)
			{
				_segment2 = null;
			} else if (_segment2 == null && _segment1 != null && _segment1 == s)
			{
				_segment1 = null;
			}
		}
		
		/** This function checks to see whether the given point is within a margin of error of the segment 
		*/
		override public function process (point:Point):void
		{
			var segment:DrawSegment = overSegment;
			var rpoint:Point, ipoint:Point = point;
			var nsegment1:DrawSegment, nsegment2:DrawSegment;  // used for candidate segments
			var rot:Number = 0;
			var _marginOfError_snap:Number = marginOfError_snap;
			// some preliminary work based on phase: 1) no magnet set  2) first magnet set 3) second magnet set
			// 1) no magnet set
			if (_segment1 == null)
			{
				// do we have candidate segment?
				if (segment != null)
				{
					// have we updated the color of the magnet?
					if (!magnet1good)
					{
						magnet1 = drawMagnet(1,magnet1);
						magnet1good = true; // so we don't change the graphics every frame
					}
				}// no candidate
				else
				{
					if (magnet1good)
					{ // revert magnet to neutral
						magnet1 = drawMagnet(0, magnet1);
						magnet1good = false; // so we don't change the graphics every frame
					}
				}
				rpoint = point;
				nsegment1 = segment; // input segment is the one we will be adjusting
			} // 1) Magnet1 set 
			else if (_segment1 != null && _segment2 == null)
			{
				// do we have a candidate segment
				if (segment != null && segment != _segment1)
				{
					// have we set either the magnet2good or magnet2bad flags (and therefore do not need further processing?)
					if (!magnet2good && !magnet2bad)
					{
						// good or bad
						//trace("difference in rotation between segments", MathUtilities.calcAcuteRotationAbs(segment.rotation, rotation), "compared to", marginOfError_parallel);
						//if ( Math.abs((segment.rotation+180)%180 - (rotation+180)%180) <= marginOfError_parallel)
						if (!_segment1.isAdjacentTo(segment) && MathUtilities.calcAcuteRotationAbs(segment.rotation, rotation) <= marginOfError_parallel && !segment.pinned && !(segment.point1.pinned && segment.point2.pinned))
						{ // good
							magnet2 = drawMagnet(1, magnet2);
							magnet2good = true; // so we don't change the graphics every frame
						} else
						{ // bad
							magnet2 = drawMagnet(-1, magnet2);	
							if (_segment1.isAdjacentTo(segment))
							{
								
							}
							else if (!endpointsPinnedMessageGiven && (segment.pinned || (segment.point1.pinned && segment.point2.pinned)))
							{
								GeometryGameItem(parent.parent).newMessage(this, "This side can't be moved.", magnet2.x, magnet2.y, MessageBox.EPHEMERAL_TYPE);
								endpointsPinnedMessageGiven = true;
							} else if (!marginOfErrorMessageGiven && MathUtilities.calcAcuteRotationAbs(segment.rotation, rotation) > marginOfError_parallel)
							{	
								GeometryGameItem(parent.parent).newMessage(this, "These two sides aren't close enough to parallel yet, try dragging first.", magnet2.x, magnet2.y, MessageBox.EPHEMERAL_TYPE);
								marginOfErrorMessageGiven = true;
							}
								
							magnet2bad = true; // so we don't change the graphics every frame
						}
					}
				} else 
				{ /// No segment here
					if (magnet2good || magnet2bad)
					{
						magnet2 = drawMagnet(0, magnet2);	
						magnet2good = false; // so we don't change the graphics every frame
						magnet2bad = false; // so we don't change the graphics every frame
					}
				}
				// setup for manipulation
				nsegment1 = _segment1;
				nsegment2 = segment; // may be null
				_marginOfError_snap = 1000; // as big as it needs to be
				rpoint = point;
			} // 3) Segment1 and Segment2 are set
			else if (_segment2 != null)
			{
				nsegment1 = _segment1;
				nsegment2 = _segment2;
				_marginOfError_snap = 1000; // as big as it needs to be
			}
				
			//////////////////////// adjust the tool, and possibly the shapes //////////////////////
			if (nsegment1 != null)
			{
				// get degrees of _segment1
				rot = nsegment1.rotationAbs;
				//trace("rotation of segment 1", rot);
				//// calculate the distance from closest point on _segment1
				// 1) calculate distances around triangle formed by endpoints and point
				var distE1E2:Number = MathUtilities.distance(nsegment1.endpoint1.x, nsegment1.endpoint1.y, nsegment1.endpoint2.x, nsegment1.endpoint2.y);
				var distE1P:Number = MathUtilities.distance(nsegment1.endpoint1.x, nsegment1.endpoint1.y, point.x, point.y);
				var distE2P:Number = MathUtilities.distance(nsegment1.endpoint2.x, nsegment1.endpoint2.y, point.x, point.y);
				// 2) calculate angle at endpoint 1 using law of cosines
				var E1:Number = MathUtilities.lawOfCosines(distE1E2, distE1P, distE2P, 0);
				var E2:Number = MathUtilities.lawOfCosines(distE1E2, distE2P, distE1P, 0);
				var atE1:Boolean, atE2:Boolean;
				if (E1 >= Math.PI/2){ atE1 = true; } else { atE1 = false; }
				if (E2 >= Math.PI/2){ atE2 = true; } else { atE2 = false; }
				// 3) use good old sohcatoa to get distance of point to nearest point on _segment1
				//    unless we are at an endpoint, then we just go straight out from endpoint
				//if (atE1)
				//{
				//	distI = distE1P * Math.sin(E1);
				//} else if (atE2)
				//{
				//	distI = distE2P * Math.sin(E2);
				//} else
				//{
					distI = distE1P * Math.sin(E1);
				//}
				// within margin of error
				if (distI < _marginOfError_snap || _segment1 != null)
				{
					//// get the point of intersection
					// 4) we also need distances from new intersection point to both endpoints using pythag
					var distE1I:Number = Math.sqrt (distE1P*distE1P - distI*distI);
					// 5) now use similar triangles to get dx and dy(from endpoint 1)  part/whole (diagonal) = part/whole (x or y)
					// unless we are at endpoint, then we simply go straight out
					var dx1:Number, dy1:Number;
					dx1 = distE1I/distE1E2 * (nsegment1.endpoint1.x - nsegment1.endpoint2.x);
					dy1 = distE1I/distE1E2 * (nsegment1.endpoint1.y - nsegment1.endpoint2.y);
					if (atE1){ dx1*=-1; dy1*=-1;}
					ipoint = new Point(nsegment1.endpoint1.x - dx1, nsegment1.endpoint1.y - dy1);					
				}
								
				//// Once the first _segment1 has been chosen adjust the second magnet and arm
				if (_segment1 != null && _segment2 == null)
				{
					// if we are over a second segment, snap to it (length)
					if (magnet2good)
					{
						// need to calculate distance from ipoint and intersection point on segment2 (_segment2)
						//point2 = MathUtilities.intersectionPerp
						//trace(ipoint, nsegment2.endpoint1, nsegment2.endpoint2);
						point2 = MathUtilities.intersectionPerpPoints(ipoint, nsegment2.endpoint1, nsegment2.endpoint2);
						//var distI2:Number = MathUtilities.distance(ipoint2.x, ipoint2.y, 
						distI = MathUtilities.distance(ipoint.x, ipoint.y, point2.x, point2.y) ;
					}
					// place magnet 2 at closest point on line perpendicular to segment 1
					//trace("distance to new segment", distI, ipoint, point2, "rotation", this.rotation);
					distI *= -1*MathUtilities.calcAcuteDirection(nsegment1.angleAbs, MathUtilities.calcAngle(rpoint, ipoint));
					arm = drawArm(distI,arm);
					magnet2.x = distI;
				} else  if (_segment1 != null && _segment2 != null)
				{ // WE ARE WIGGLING
					rpoint = MathUtilities.intersectionPerpPoints(ipoint, _segment2.endpoint1, _segment2.endpoint2);
					// place magnet 2 at closest point on line perpendicular to segment 1
					arm = drawArm(distFixed, arm);
					magnet2.x = distFixed;
					
					// amount of wiggle from previous frame
					var thisWiggle:Number = MathUtilities.distance(this.x, this.y, previousWigglePoint.x, previousWigglePoint.y);
					// Should not exceed maxWiggleProp
					var wiggleProp:Number = thisWiggle/targetWiggle;
					if (wiggleProp < MAX_WIGGLE_PROP)
					{
						currentWiggle += thisWiggle;
					} else // in this case use .1 as maxWiggleProp and work backwards to get currentWiggle	
					{
						wiggleProp = MAX_WIGGLE_PROP;
						currentWiggle += wiggleProp * targetWiggle;
					}
					
					// have we reached target
					if (currentWiggle < targetWiggle)
					{
						//trace("currentWiggle", currentWiggle, "targetWiggle", targetWiggle, "thisWiggle", thisWiggle, "wiggleProp", wiggleProp);
						// adjust segment
						if(_segment2.rotateToDegrees(_segment1.rotation, currentWiggle/targetWiggle,rpoint))
						{
							previousWigglePoint.x = this.x;
							previousWigglePoint.y = this.y;
						} else 
						{
							//this.dispatchEvent(new Event(Event.COMPLETE));
							GeometryGameItem(parent.parent).processCommand("parallel", "in_progress", this, components);
						}							
					} else
					{
						_segment2.rotateToDegrees(_segment1.rotation, 1,rpoint);
						//this.dispatchEvent(new Event(Event.COMPLETE));
						GeometryGameItem(parent.parent).processCommand("parallel", "in_progress", this, components);
					}
				}
			}
			this.x = ipoint.x;
			this.y = ipoint.y;
			this.rotation = rot;
		}
		
		/** Draws the cursor with the relevant magnets */
		private function drawCursor():void
		{
			switch(_state)
			{
				case "zero":
					magnet1 = drawMagnet(0);
					this.addChild(magnet1);
					break;
				case "one":
					// redraw magnet1 without blue outline
					magnet1 = drawMagnet(0, magnet1);
					magnet2 = drawMagnet(0);
					this.addChild(magnet2);
					arm = drawArm(0);
					this.addChildAt(arm, 0);
					break;
				case "two":
					magnet2 = drawMagnet(0,magnet2);
					break;
			}
		}
		
		/** Draws a half of the length tool (two magnets connected)  
		 * State can be in three forms 1-good, 0-default, -1-bad */
		private function drawMagnet(state:int=0, magnet:Shape=null):Shape
		{
			if (magnet == null)
			{
				magnet = new Shape();
			} else
			{
				magnet.graphics.clear();	
			}
			// draw line
			magnet.graphics.lineStyle(1, 0x0000FF, .5);
			magnet.graphics.moveTo(0,-parent.height);
			magnet.graphics.lineTo(0,parent.height);
			// draw magnet at one end
			if (state == 1)
			{
				magnet.graphics.lineStyle(2, 0xAAAAFF);
			} else if (state == -1)
			{
				magnet.graphics.lineStyle(2, 0xFFAAAA);
			} else
			{
				magnet.graphics.lineStyle(2, 0xAAAAAA);
			}
			magnet.graphics.beginFill(0xCCCCCC);
			magnet.graphics.drawRect(-MAGNET_WIDTH/2,-MAGNET_HEIGHT/2,MAGNET_WIDTH,MAGNET_HEIGHT);
			magnet.graphics.endFill();		
			//magnet.graphics.lineStyle(2, 0xAAAAAA);
			for (var i:int=0; i < 5; i++)
			{
				magnet.graphics.moveTo(0, i*MAGNET_HEIGHT/5-MAGNET_HEIGHT/2);
				magnet.graphics.lineTo(-MAGNET_WIDTH/2, (i+1)*MAGNET_HEIGHT/5-MAGNET_HEIGHT/2);
				magnet.graphics.moveTo(0, i*MAGNET_HEIGHT/5-MAGNET_HEIGHT/2);
				magnet.graphics.lineTo(MAGNET_WIDTH/2, (i+1)*MAGNET_HEIGHT/5-MAGNET_HEIGHT/2);
			}
			return magnet;
		}
		
		private function drawArm(armLength:Number, arm:Shape=null):Shape
		{
			if (arm == null)
			{
				arm = new Shape();
			} else
			{
				arm.graphics.clear();
			}
			arm.graphics.lineStyle(1,0xAAAAAA);
			arm.graphics.beginFill(0xDDDDDD);
			if (armLength > 0)
			{
				arm.graphics.drawRect(0, -ARM_WIDTH/2, Math.abs(armLength), ARM_WIDTH);	
			} else if (armLength < 0)
			{
				arm.graphics.drawRect(armLength, -ARM_WIDTH/2, Math.abs(armLength), ARM_WIDTH);	
			}
			arm.graphics.endFill();
			arm.graphics.lineStyle(1,0xAAAAAA);
			arm.graphics.moveTo(0,0);
			arm.graphics.lineTo(armLength,0);
			return arm;
		}
		
		private function handleRemoved(event:Event):void
		{
			this.removeEventListener(Event.REMOVED, handleRemoved);
			// go through each point and add event listener
			for each (var s:DrawSegment in drawSegments)
			{
				if(s.hasEventListener(MouseEvent.MOUSE_OVER)) s.removeEventListener(MouseEvent.MOUSE_OVER, handleMouseOverSegment);
				if(s.hasEventListener(MouseEvent.MOUSE_OUT)) s.removeEventListener(MouseEvent.MOUSE_OUT, handleMouseOutSegment);
				if(s.hasEventListener(MouseEvent.CLICK)) s.removeEventListener(MouseEvent.CLICK, handleClickSegment);
				s.mouseEnabled = false;
			}
			for each (var oobshape:OobShape in oobshapes)
			{
				if(oobshape.hasEventListener(MouseEvent.MOUSE_OVER)) oobshape.removeEventListener(MouseEvent.MOUSE_OVER, handleMouseOverOobShape);				
				if(oobshape.hasEventListener(MouseEvent.MOUSE_OUT)) oobshape.removeEventListener(MouseEvent.MOUSE_OUT, handleMouseOutOobShape);				
			}			
		}
	}	
}